// i.d(t, { K: () => NavigationUrl })
import * as THREE from "three"
import browserUtil from "./browser.util"
import Logger from "./Logger"
import a from "../math/19674"
import { isPanOrMesh } from "./viewmode.util"
import funcUtil from "./func.util"
import viewmodeEnum from "../enum/viewmode.enum"
import SweepData from "../data/SweepData"

const c = new Logger("navurl")
class InvalidViewmodeError extends Error {
  constructor(e, t?) {
    super(`view mode ${e} is invalid${t ? ` when ${t}` : ""}`)
  }
}
export default class NavigationUrl {
  static DEFAULT_VIEW_MODE: number
  static VALID_URL_VIEW_MODES: number[]
  static encodeVector3(e: THREE.Vector3, t = 2) {
    const { x: i, y: n, z: s } = e
    return NavigationUrl.packFloats([i, n, s], t)
  }
  static decodeVector3(e: string) {
    const t = NavigationUrl.unpackFloats(e, 3)
    return t ? new THREE.Vector3(...t) : null
  }
  static encodeQuaternion(e: THREE.Quaternion, t = 2) {
    return NavigationUrl.packFloats([e.x, e.y, e.z, e.w], t)
  }
  static decodeQuaternion(e: string) {
    const t = NavigationUrl.unpackFloats(e, 4)
    return t && !funcUtil.isNullOBJ(t) ? new THREE.Quaternion(...t) : null
  }
  static encodeEuler(e: THREE.Euler, t = 2) {
    return NavigationUrl.packFloats([e.x, e.y, e.z], t)
  }
  static decodeEuler(e: string) {
    const t = NavigationUrl.unpackFloats(e, 3)
    return t ? new THREE.Euler(t[0], t[1], t[2], "XYZ") : void 0
  }
  static packFloats(e: number[], t = 2) {
    let i = e
      .map(e => {
        const i = parseFloat(e.toFixed(t))
          .toString()
          .replace(/^(\-)?0+.(\d+)/, "$1.$2")
        return "0" === i ? "" : i
      })
      .join(",")
    return (i = i.replace(/,+$/, "")), i
  }
  static unpackFloats(e: string, t = 0) {
    const i = e.split(",").map(e => ("" === e ? 0 : parseFloat(e)))
    if (!i || i.includes(NaN)) return []
    for (; i.length < t; ) i.push(0)
    return i
  }
  static getQueryString(e: any, t = !0) {
    const i = NavigationUrl.serialize(e, t)
    return Object.keys(i)
      .map(e => e + "=" + i[e])
      .join("&")
  }
  static serialize(e: any, t = !0) {
    const i = NavigationUrl.removeNavigationKeys(browserUtil.queryToMap())
    try {
      if (t) {
        const t = NavigationUrl.getShortPose(e)
        Object.assign(i, t)
      } else {
        const t = NavigationUrl.getStartPose(e),
          n = encodeURIComponent(JSON.stringify(t))
        Object.assign(i, { start: n })
      }
    } catch (e) {
      c.debug(e)
    }
    return i
  }
  static deserialize(e?) {
    const t: any = browserUtil.queryToMap(e)
    try {
      return "start" in t ? NavigationUrl.parseStartObject(t.start) : NavigationUrl.parseShortUrl(t)
    } catch (e) {
      c.debug(e)
    }
    return null
  }
  static removeNavigationKeys(e: any) {
    const t = Object.assign({}, e)
    for (const e of ["start", "sm", "sp", "sq", "sr", "ss", "sf", "sz"]) delete t[e]
    return t
  }
  static parseShortUrl(e) {
    const { sm: t, sp: i, sq: r, sr: o, ss: l, sf: h, sz: d } = e,
      u = NavigationUrl.parseViewmode(t),
      p = NavigationUrl.decodeEuler(o || "") || new THREE.Euler(0, 0, 0),
      m = NavigationUrl.decodeQuaternion(r || "") || new THREE.Quaternion(0, 0, 0, 1),
      g = i ? NavigationUrl.decodeVector3(i) : void 0,
      f = l ? parseInt(l, 10) - 1 : void 0,
      v = h ? NavigationUrl.unpackFloats(h, 4) : void 0,
      y = d ? parseFloat(d) : void 0
    if (isPanOrMesh(u) && void 0 === f) throw new InvalidViewmodeError(u, "sweep index not defined")
    if (!isPanOrMesh(u) && !g) throw new InvalidViewmodeError(u, "position is falsy")
    let b = r ? m : new THREE.Quaternion().setFromEuler(p)
    isPanOrMesh(u) && (b = a.Z(b))
    const S = {
      mode: u,
      quaternion: b,
      position: g || void 0,
      sweepIndex: f,
      floorVisibility: v,
      zoom: y
    }
    return c.debug("Parsed short URL pose data:", S), S
  }
  static parseStartObject(e) {
    const t = JSON.parse(decodeURIComponent(e))
    if (!t.camera_quaternion) throw Error("Start override is missing camera_quaternion")
    if (!t.camera_position) throw Error("Start override is missing camera_position")
    const i = NavigationUrl.parseViewmode(t.camera_mode),
      s = new THREE.Quaternion().copy(t.camera_quaternion),
      r = {
        mode: i,
        panoId: t.scan_id,
        position: new THREE.Vector3().copy(t.camera_position),
        quaternion: s,
        zoom: t.ortho_zoom,
        floorVisibility: t.floor_visibility
      }
    return c.debug("Parsed &start URL pose data:", r), r
  }
  static getShortPose({ cameraData: e, viewmodeData: t, floorsViewData: i, sweepData: r }) {
    const o = new THREE.Euler().setFromQuaternion(e.pose.rotation, "XYZ")
    const a: SweepData["currentSweepIndex"] = r.currentSweepIndex
    const l: any = {}
    if (t.isInside())
      (o.z = 0),
        (l.sr = NavigationUrl.encodeEuler(o)),
        void 0 !== a && (l.ss = a + 1),
        t.currentMode === viewmodeEnum.Mesh && (l.sm = NavigationUrl.validateViewmode(t.currentMode))
    else {
      l.sm = NavigationUrl.validateViewmode(t.currentMode)
      l.sr = NavigationUrl.encodeEuler(o)
      l.sp = NavigationUrl.encodeVector3(e.pose.position)
      const n = i.getFloorsVisibility()
      n.length && i.totalFloors > 1 && (l.sf = n)
    }
    t.currentMode === viewmodeEnum.Floorplan && (l.sz = parseFloat(e.zoom().toFixed(2)))
    l.sq = NavigationUrl.encodeQuaternion(e.pose.rotation)
    c.debug(`Short URL params for current location ${JSON.stringify(l)}`)
    delete l.sq
    return l
  }
  static getStartPose({ cameraData: e, viewmodeData: t, floorsViewData: i, sweepData: n }) {
    const r = { x: 0, y: 0, z: 0, w: 1 }
    ;(r.x = e.pose.rotation.x), (r.y = e.pose.rotation.y), (r.z = e.pose.rotation.z), (r.w = e.pose.rotation.w)
    const o = { x: 0, y: 0, z: 0 }
    ;(o.x = e.pose.position.x), (o.y = e.pose.position.y), (o.z = e.pose.position.z)
    const a = {
      camera_mode: NavigationUrl.validateViewmode(t.currentMode),
      camera_position: o,
      camera_quaternion: r,
      scan_id: null,
      floor_visibility: null,
      ortho_zoom: null
    }
    t.isInside() ? (a.scan_id = n.currentSweep) : (a.floor_visibility = i.getFloorsVisibility())
    t.currentMode === viewmodeEnum.Floorplan && (a.ortho_zoom = e.zoom())
    return a
  }
  static parseViewmode(e) {
    let t = NavigationUrl.DEFAULT_VIEW_MODE
    !!e && (t = parseInt(e, 10))
    return NavigationUrl.validateViewmode(t)
  }
  static validateViewmode(e?: number) {
    if (null === e || !NavigationUrl.VALID_URL_VIEW_MODES.includes(e)) throw new InvalidViewmodeError(e)
    return e
  }
}
NavigationUrl.DEFAULT_VIEW_MODE = viewmodeEnum.Panorama
NavigationUrl.VALID_URL_VIEW_MODES = [viewmodeEnum.Panorama, viewmodeEnum.Dollhouse, viewmodeEnum.Floorplan, viewmodeEnum.Outdoor, viewmodeEnum.Mesh]
